package schuchert.contest;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import com.ibm.contest.instrumentation.ClassStreamInstrumentor;
import com.ibm.contest.instrumentation.InstrumentationAction;

public class DynamicInstrumentor implements ClassFileTransformer {
    public static final String CON_TEST_INSTRUMENTATION_CLASS_FILTER = "schuchert.DI.classFilter";
    public static final String CON_TEST_INSTRUMENTATION_DELETE_FILES = "schuchert.DI.deleteFiles";
    public static final String CON_TEST_INSTRUMENTATION_TEMP_DIR = "schuchert.DI.tempDir";
    String tempDirName = "TEMP_DIR";

    File tempDirectory;

    boolean deleteFilesAfterTransformation = true;
    List<String> filterPatterns = Collections.emptyList();

    public DynamicInstrumentor() {
        setDefaultClassFilter();
        processProperties();
        setupTempDirectory();
    }

    private void setDefaultClassFilter() {
        setFilter("-com.*::-java.*::-org.*");
    }

    private void setupTempDirectory() {
        try {
            tempDirectory = FileUtilities.createTemporaryDirectory(tempDirName);
            if (deleteFilesAfterTransformation)
                tempDirectory.deleteOnExit();
        } catch (Exception e) {
            e.printStackTrace(System.err);
            throw new RuntimeException(e);
        }
    }

    private void processProperties() {
        String property = System.getProperty(CON_TEST_INSTRUMENTATION_DELETE_FILES);
        if (property != null)
            deleteFilesAfterTransformation = Boolean.parseBoolean(property);

        property = System.getProperty(CON_TEST_INSTRUMENTATION_TEMP_DIR);
        if (property != null)
            tempDirName = property;

        property = System.getProperty(CON_TEST_INSTRUMENTATION_CLASS_FILTER);
        if (property != null)
            setFilter(property);
    }

    File getTempDirectory() {
        return tempDirectory;
    }

    public byte[] transform(ClassLoader loader, String fullyQualifiedClassName,
            Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer)
            throws IllegalClassFormatException {

        if (isClassWeShouldProcess(fullyQualifiedClassName)) {
            ByteArrayInputStream inFile = new ByteArrayInputStream(classfileBuffer);

            try {
                ClassStreamInstrumentor instrumentor = new ClassStreamInstrumentor(inFile,
                        fullyQualifiedClassName, true);
                ByteArrayOutputStream outFile = new ByteArrayOutputStream(
                        4 * classfileBuffer.length);
                instrumentor.instrumentInto(outFile, new InstrumentationAction(null, null));
                return outFile.toByteArray();
            } catch (Exception e) {
                e.printStackTrace(System.err);
            } finally {
                FileUtilities.closeIgnoringIoException(inFile);
            }
        }

        return null;
    }

    private void cleanupTemporaryFiles(File originalBytesFile) {
        if (deleteFilesAfterTransformation) {
            FileUtilities.removeFile(originalBytesFile);
            File generatedBackupFile = new File(originalBytesFile.getAbsoluteFile() + "_backup");
            FileUtilities.removeFile(generatedBackupFile);
        }
    }

    boolean isClassWeShouldProcess(String fullyQualifiedClassname) {
        boolean noMatchingPatternResponse = true;

        for (String pattern : filterPatterns) {
            char operation = pattern.charAt(0);
            pattern = pattern.substring(1);
            switch (operation) {
            case '+':
                noMatchingPatternResponse = false;
                if (fullyQualifiedClassname.matches(pattern))
                    return true;
                break;
            case '-':
                if (fullyQualifiedClassname.matches(pattern))
                    return false;
                break;
            default:
                System.err.printf(
                        "Invalid pattern: %s for selecting classes to/to not instrument.", pattern);
            }
        }

        return noMatchingPatternResponse;
    }

    public void setFilter(String string) {
        String[] splitPatterns = null;

        if (string == null || string.trim().length() == 0)
            splitPatterns = new String[] {};
        else
            splitPatterns = string.split("::");

        filterPatterns = new ArrayList<String>();
        for (String currentString : splitPatterns) {
            if (currentString == null || currentString.trim().length() == 0)
                continue;
            filterPatterns.add(currentString.trim());
        }

    }
}
